package ar.com.profertil.utils.service;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.math.BigInteger;

import org.bouncycastle.crypto.AsymmetricBlockCipher;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.digests.MD5Digest;
import org.bouncycastle.crypto.encodings.PKCS1Encoding;
import org.bouncycastle.crypto.engines.RSAEngine;
import org.bouncycastle.crypto.params.RSAKeyParameters;
import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
import org.bouncycastle.util.encoders.Hex;

import ar.com.profertil.utils.certificado.Certificado;

/**
 * The Class RSABC.
 */
public class RSABC {

    /**
     * Obtener m d5.
     *
     * @param path the path where the file is 
     * @param inputFile the input file
     * @return the string
     * @throws Exception the exception
     */
    public static String obtenerMD5(String path, String inputFile) throws Exception {
        Digest digest = new MD5Digest();
        byte resBuf[] = new byte[digest.getDigestSize()];
        try {
            FileInputStream streamF = new FileInputStream(path + inputFile);
            int tamanoBuffer = 1024;
            byte arrayBytes[] = new byte[tamanoBuffer];
            while(streamF.available() != 0) {
                int cantidad = streamF.available();
                if(cantidad <= tamanoBuffer - 1) {
                    int aux = streamF.available();
                    streamF.read(arrayBytes, 0, aux);
                    digest.update(arrayBytes, 0, aux);
                } else {
                    streamF.read(arrayBytes, 0, tamanoBuffer);
                    digest.update(arrayBytes, 0, tamanoBuffer);
                }
            }
            streamF.close();
        } catch(Exception e) {
        	throw e;
        }
        digest.doFinal(resBuf, 0);
        return new String(Hex.encode(resBuf));
    }
    
    /**
     * Obtener m d5.
     *
     * @param input the input
     * @return the string
     * @throws Exception the exception
     */
    public static String obtenerMD5(String input) throws Exception {
        Digest digest = new MD5Digest();
        byte resBuf[] = new byte[digest.getDigestSize()];
        try {
        	byte[] arrayBytes = input.getBytes();
        	digest.update(arrayBytes, 0, arrayBytes.length);
        } catch(Exception e) {
        	throw e;
        }
        digest.doFinal(resBuf, 0);
        return new String(Hex.encode(resBuf));
    }

    /**
     * Encriptar string largo.
     *
     * @param input the input
     * @param certificado the certificado
     * @param keyStorageView the key storage view
     * @param sAlias the alias of a key in the key storage view
     * @param pass the pass
     * @return the string
     * @throws Exception the exception
     */
    public static String encriptarStringLargo(String input, String certificado, String keyStorageView, String sAlias, String pass) throws Exception {
        RSAKeyParameters parameters = null;
        String resultado = "";
        Certificado cert = new Certificado(null, pass, keyStorageView, sAlias, true);
        mod = cert.getClavePrivada().getModulus();
        pubExp = cert.getClavePrivada().getPublicExp();
        privExp = cert.getClavePrivada().getPrivateExp();
        p = cert.getClavePrivada().getPrimeP();
        q = cert.getClavePrivada().getPrimeQ();
        pExp = cert.getClavePrivada().getPrimeExpP();
        qExp = cert.getClavePrivada().getPrimeExpQ();
        crtCoef = cert.getClavePrivada().getCRTCoefficient();
        parameters = new RSAPrivateCrtKeyParameters(mod, pubExp, privExp, p, q, pExp, qExp, crtCoef);
        AsymmetricBlockCipher eng = new RSAEngine();
        eng = new PKCS1Encoding(eng);
        eng.init(true, parameters);
        try {
        	ByteArrayInputStream streamF = new ByteArrayInputStream(input.getBytes("UTF-8"));
            ByteArrayOutputStream fos = new ByteArrayOutputStream();
            int tamanoBuffer = eng.getInputBlockSize();
            byte arrayBytes[] = new byte[tamanoBuffer];
            byte datosEncriptados[] = (byte[])null;
            while(streamF.available() != 0) {
                int cantidad = streamF.available();
                if(cantidad <= tamanoBuffer - 1) {
                    int aux = streamF.available();
                    streamF.read(arrayBytes, 0, aux);
                    datosEncriptados = (byte[])null;
                    datosEncriptados = eng.processBlock(arrayBytes, 0, aux);
                    fos.write(datosEncriptados, 0, datosEncriptados.length);
                } else {
                    streamF.read(arrayBytes, 0, tamanoBuffer);
                    datosEncriptados = eng.processBlock(arrayBytes, 0, arrayBytes.length);
                    fos.write(datosEncriptados, 0, datosEncriptados.length);
                }
            }
            resultado = new String(Hex.encode(fos.toByteArray()));
        }
        catch(Exception e) {
            throw e;
        }
        return resultado;
    }

    /**
     * Desencriptar string largo.
     *
     * @param input the input
     * @param modulo the modulo
     * @param expPub the exp pub
     * @return the string
     * @throws Exception the exception
     */
    public static String desencriptarStringLargo(String input, BigInteger modulo, BigInteger expPub) throws Exception {
        RSAKeyParameters parameters = null;
        String stringDesencriptado = "";
        mod = modulo;
        pubExp = expPub;
        parameters = new RSAKeyParameters(false, mod, pubExp);
        AsymmetricBlockCipher eng = new RSAEngine();
        eng = new PKCS1Encoding(eng);
        eng.init(false, parameters);
        try {
//            byte ba[] = Hex.decode(input);
            byte ba[] = input.getBytes();
            ByteArrayInputStream streamF = new ByteArrayInputStream(ba);
            ByteArrayOutputStream fos = new ByteArrayOutputStream();
            int tamanoBuffer = eng.getInputBlockSize();
            byte arrayBytes[] = new byte[tamanoBuffer];
            byte datosDesEncriptados[] = (byte[])null;
            while(streamF.available() != 0 && streamF.available() > tamanoBuffer) {
                streamF.read(arrayBytes, 0, tamanoBuffer);
                datosDesEncriptados = eng.processBlock(arrayBytes, 0, arrayBytes.length);
                fos.write(datosDesEncriptados, 0, datosDesEncriptados.length);
                for(int i = 0; i < datosDesEncriptados.length; i++) {
                	stringDesencriptado = stringDesencriptado + (char)datosDesEncriptados[i];
                }
            }
            streamF.read(arrayBytes, 0, tamanoBuffer);
            datosDesEncriptados = eng.processBlock(arrayBytes, 0, arrayBytes.length);
            fos.write(datosDesEncriptados, 0, datosDesEncriptados.length);
            for(int i = 0; i < datosDesEncriptados.length; i++) {
            	stringDesencriptado = stringDesencriptado + (char)datosDesEncriptados[i];
            }
        } catch(Exception e) {
            throw e;
        }
        return stringDesencriptado;
    }

    /**
     * Obtener datos publicos.
     * Carga el certificado en memoria y setea los datos propios del certificado.
     * @param certificado Path completo donde esta el certificado
     * @param keyStorageView the key storage view
     * @param sAlias the alias of a key in the key storage view
     * @param pass con el que fue encriptado el certificado
     * @return the string
     * @throws Exception the exception
     */
    public static String obtenerDatosPublicos(String certificado, String keyStorageView, String sAlias, String pass) throws Exception  {
        Certificado cert = new Certificado(null, pass, keyStorageView, sAlias, true);
        mod = cert.getClavePrivada().getModulus();
        pubExp = cert.getClavePrivada().getPublicExp();
        privExp = cert.getClavePrivada().getPrivateExp();
        p = cert.getClavePrivada().getPrimeP();
        q = cert.getClavePrivada().getPrimeQ();
        pExp = cert.getClavePrivada().getPrimeExpP();
        qExp = cert.getClavePrivada().getPrimeExpQ();
        crtCoef = cert.getClavePrivada().getCRTCoefficient();
    	return mod + "-" + pubExp + "-";
    }

    /**
     * Encriptar string.
     *
     * @param encriptar the encriptar
     * @param cadena the cadena
     * @param usePrivate the use private
     * @return the string
     * @throws Exception the exception
     */
    public static String encriptarString(boolean encriptar, 
    									 String cadena, 
    									 boolean usePrivate) throws Exception {
        RSAKeyParameters parameters = null;
        if(usePrivate) {
        	parameters = new RSAPrivateCrtKeyParameters(mod, pubExp, privExp, p, q, pExp, qExp, crtCoef);
        } else {
        	parameters = new RSAKeyParameters(false, mod, pubExp);
        }
        byte data[] = (byte[])null;
        if(encriptar) {
        	data = cadena.getBytes();
        } else {
        	data = Hex.decode(cadena);
        }
        AsymmetricBlockCipher eng = new RSAEngine();
        eng = new PKCS1Encoding(eng);
        eng.init(encriptar, parameters);
        try {
            data = eng.processBlock(data, 0, data.length);
        } catch(Exception e) {
            throw e;
        }
        String pp = "";
        for(int i = 0; i < data.length; i++) {
        	pp = pp + (char)data[i];
        }

        if(encriptar) {
        	return new String(Hex.encode(data));
        } else {
        	return pp;
        }
    }
    
    /**
     * Encriptar string.
     *
     * @param encriptar the encriptar
     * @param cadena the cadena
     * @param usePrivate the use private
     * @return the string
     * @throws Exception the exception
     */
    public static String encriptarString(boolean encriptar, 
    									 byte[] cadena, 
    									 boolean usePrivate) throws Exception {
        RSAKeyParameters parameters = null;
        if(usePrivate) {
        	parameters = new RSAPrivateCrtKeyParameters(mod, pubExp, privExp, p, q, pExp, qExp, crtCoef);
        } else {
        	parameters = new RSAKeyParameters(false, mod, pubExp);
        }
        byte data[] = (byte[])null;
        if(encriptar) {
        	data = cadena;
        } else {
        	data = Hex.decode(cadena);
        }
        AsymmetricBlockCipher eng = new RSAEngine();
        eng = new PKCS1Encoding(eng);
        eng.init(encriptar, parameters);
        try {
            data = eng.processBlock(data, 0, data.length);
        } catch(Exception e) {
            throw e;
        }
        String pp = "";
        for(int i = 0; i < data.length; i++) {
        	pp = pp + (char)data[i];
        }

        if(encriptar) {
        	return new String(Hex.encode(data));
        } else {
        	return pp;
        }
    }
    
    /**
     * Encriptar string.
     *
     * @param encriptar the encriptar
     * @param cadena the cadena
     * @param usePrivate the use private
     * @return the string
     * @throws Exception the exception
     */
    public static String encriptarStringPorPartes(boolean encriptar, 
    									 			String cadena, 
    									 			boolean usePrivate) throws Exception {
        RSAKeyParameters parameters = null;
        if(usePrivate) {
        	parameters = new RSAPrivateCrtKeyParameters(mod, pubExp, privExp, p, q, pExp, qExp, crtCoef);
        } else {
        	parameters = new RSAKeyParameters(false, mod, pubExp);
        }
        String pp = "";
        try {
	        if(encriptar) {
		        AsymmetricBlockCipher eng = new RSAEngine();
		        eng = new PKCS1Encoding(eng);
		        eng.init(encriptar, parameters);
		        
		        int blockSize = eng.getInputBlockSize();
		        int numWholeBlocks = cadena.length() / blockSize;
		        int sizeLastBlock = cadena.length() % blockSize;
		        int numBlocks;
		        if (sizeLastBlock > 0) {
		            numBlocks = numWholeBlocks + 1;
		        } else {
		            numBlocks = numWholeBlocks;
		            sizeLastBlock=blockSize;
		        }
		        byte[] cadenaByte = cadena.getBytes();
		        byte data[] = new byte[numBlocks * blockSize];
		        byte[] aux;
		        
		        int WN = numBlocks - numWholeBlocks;
		
		        for (int i = 0; i < numWholeBlocks; i++) {
		            aux = eng.processBlock(cadenaByte, i * blockSize, blockSize);
		            System.arraycopy(aux, 0, data, i * blockSize, blockSize);
		        }
		
		        for (int i = 0; i < WN; i++) {
		            aux = eng.processBlock(cadenaByte, i * blockSize, sizeLastBlock);
		            System.arraycopy(aux, 0, data, numWholeBlocks * blockSize, sizeLastBlock);
		        }
		        pp = new String(Hex.encode(data));
	        }
	        else{
	        	AsymmetricBlockCipher eng = new RSAEngine();
		        eng = new PKCS1Encoding(eng);
		        eng.init(encriptar, parameters);
		        
		        int blockSize = eng.getInputBlockSize();
		        int numWholeBlocks = cadena.length() / blockSize;
		        int sizeLastBlock = cadena.length() % blockSize;
		        int numBlocks;
		        if (sizeLastBlock > 0) {
		            numBlocks = numWholeBlocks + 1;
		        } else {
		            numBlocks = numWholeBlocks;
		            sizeLastBlock=blockSize;
		        }
		        byte[] cadenaByte = Hex.decode(cadena);
		        byte data[] = new byte[numBlocks * blockSize];
		        byte[] aux;
		        
		        int WN = numBlocks - numWholeBlocks;
		
		        for (int i = 0; i < numWholeBlocks; i++) {
		            aux = eng.processBlock(cadenaByte, i * blockSize, blockSize);
		            System.arraycopy(aux, 0, data, i * blockSize, blockSize);
		        }
		
		        for (int i = 0; i < WN; i++) {
		            aux = eng.processBlock(cadenaByte, i * blockSize, sizeLastBlock);
		            System.arraycopy(aux, 0, data, numWholeBlocks * blockSize, sizeLastBlock);
		        }
		        for(int i = 0; i < data.length; i++) {
		        	pp = pp + (char)data[i];
		        }
	        }
        } catch(Exception e) {
            throw e;
        }

        return pp;
    }

    /**
     * Procesar string pub cert.
     *
     * @param encriptar the encriptar
     * @param keyStorageView the key storage view
     * @param sAlias the alias of a key in the key storage view
     * @param password the password
     * @param cadena the cadena
     * @return the string
     * @throws Exception the exception
     */
    public static String procesarStringPubCert(boolean encriptar,
    										   String keyStorageView, 
    										   String sAlias,
    										   String password, 
    										   String cadena) throws Exception {
        Certificado cert = new Certificado(null, password, keyStorageView, sAlias, true);
        mod = cert.getClavePrivada().getModulus();
        pubExp = cert.getClavePrivada().getPublicExp();
        return encriptarString(encriptar, cadena, false);
    }
    
    /**
     * Procesar string pub cert.
     *
     * @param encriptar the encriptar
     * @param keyStorageView the key storage view
     * @param sAlias the alias of a key in the key storage view
     * @param password the password
     * @param cadena the cadena
     * @return the string
     * @throws Exception the exception
     */
    public static String procesarStringPubCertPorPartes(boolean encriptar,
    										   		String keyStorageView, 
    										   		String sAlias,
    										   		String password, 
    										   		String cadena) throws Exception {
        Certificado cert = new Certificado(null, password, keyStorageView, sAlias, true);
        mod = cert.getClavePrivada().getModulus();
        pubExp = cert.getClavePrivada().getPublicExp();
        return encriptarStringPorPartes(encriptar, cadena, false);
    }

    /**
     * Procesar string priv.
     *
     * @param encriptar the encriptar
     * @param certificado the certificado
     * @param keyStorageView the key storage view
     * @param sAlias the alias of a key in the key storage view
     * @param password the password
     * @param cadena the cadena
     * @return the string
     * @throws Exception the exception
     */
    public static String procesarStringPriv(boolean encriptar, 
    										String certificado,
    								   		String keyStorageView,
    								   		String sAlias, 
    										String password, 
    										String cadena) throws Exception {
        Certificado cert = new Certificado(null, password, keyStorageView, sAlias, true);
        mod = cert.getClavePrivada().getModulus();
        pubExp = cert.getClavePrivada().getPublicExp();
        privExp = cert.getClavePrivada().getPrivateExp();
        p = cert.getClavePrivada().getPrimeP();
        q = cert.getClavePrivada().getPrimeQ();
        pExp = cert.getClavePrivada().getPrimeExpP();
        qExp = cert.getClavePrivada().getPrimeExpQ();
        crtCoef = cert.getClavePrivada().getCRTCoefficient();
        return encriptarString(encriptar, cadena, true);
    }
    
    /**
     * Procesar string pub cert.
     *
     * @param encriptar the encriptar
     * @param keyStorageView the key storage view
     * @param sAlias the alias of a key in the key storage view
     * @param password the password
     * @param cadena the cadena
     * @return the string
     * @throws Exception the exception
     */
    public static String procesarStringPubCert(boolean encriptar,
    										   String keyStorageView, 
    										   String sAlias,
    										   String password, 
    										   byte[] cadena) throws Exception {
        Certificado cert = new Certificado(null, password, keyStorageView, sAlias, true);
        mod = cert.getClavePrivada().getModulus();
        pubExp = cert.getClavePrivada().getPublicExp();
        return encriptarString(encriptar, cadena, false);
    }
 
    /** The mod. */
    private static BigInteger mod = null;
    
    /** The pub exp. */
    private static BigInteger pubExp = null;
    
    /** The priv exp. */
    private static BigInteger privExp = null;
    
    /** The p. */
    private static BigInteger p = null;
    
    /** The q. */
    private static BigInteger q = null;
    
    /** The p exp. */
    private static BigInteger pExp = null;
    
    /** The q exp. */
    private static BigInteger qExp = null;
    
    /** The crt coef. */
    private static BigInteger crtCoef = null;
    
	/**
	 * Gets the mod.
	 *
	 * @return the mod
	 */
	public static BigInteger getMod() {
		return mod;
	}
	
	/**
	 * Gets the pub exp.
	 *
	 * @return the pub exp
	 */
	public static BigInteger getPubExp() {
		return pubExp;
	}
}
